﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Xml;
using System.IO;
using System.Resources;
using System.Reflection;
using System.ComponentModel;

namespace DailyRssDownload.Lib {
    public class Newspaper {
		public Settings Settings { get; set; }
		public BackgroundWorker BackgroundWorker { get; set; }

		private DateTime NewsDate { get; set; }
		private string NewsTitle { get; set; }
		private string NewsDateDesc { get; set; }
		
		private int _intTotalFeeds = 0;
		private int _intCurrentFeed;

		private static readonly string DC_METADATA_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";

		public Newspaper(Settings objSettings) {
			this.Settings = objSettings;
			_intTotalFeeds = Settings.Feeds.Count;
		}

		private void ReportFeedProgress(object sender, ProgressChangedEventArgs e) {
			// right now it might be null if this is a console application
			if (BackgroundWorker != null) {
				int intFeedPercentComplete = e.ProgressPercentage;
				int intTotalPercentComplete = (int)(_intCurrentFeed / (decimal)_intTotalFeeds) * intFeedPercentComplete;
				ReportProgress(intTotalPercentComplete, e.UserState);
			}
		}

		private void ReportProgress(int intPercentComplete, object objState) {
			if (BackgroundWorker != null) {
				BackgroundWorker.ReportProgress(intPercentComplete, objState);
			} else {
				Console.WriteLine("{0}% Complete.  {1}", intPercentComplete, objState);
			}
		}

		private static string GetDownloadLocation(string strBaseDownloadLocation, int intDownloadsAlreadyToday) {
			string strNextAttemptedDownloadLocation = String.Format("{0}-{1}", strBaseDownloadLocation, intDownloadsAlreadyToday);
			if (Directory.Exists(strNextAttemptedDownloadLocation))
				return GetDownloadLocation(strBaseDownloadLocation, intDownloadsAlreadyToday + 1);
			else
				return strNextAttemptedDownloadLocation;
		}

		private static string GetDownloadLocation(string strBaseDownloadLocation) {
			string strDate = DateTime.Now.ToString("yyyy-MM-dd");

			string strFirstAttemptedDownloadLocation = Path.Combine(strBaseDownloadLocation, strDate);
			if (Directory.Exists(strFirstAttemptedDownloadLocation))
				return GetDownloadLocation(strFirstAttemptedDownloadLocation, 1);
			else
				return strFirstAttemptedDownloadLocation;
		}
		
		public void DownloadNow() {
			this.NewsDate = DateTime.Now;
			this.NewsDateDesc = String.Format("{0:dddd, MMMM d, yyyy}", this.NewsDate);
			this.NewsTitle = "My Daily News";

			_intCurrentFeed = 1;
			_intTotalFeeds = Settings.Feeds.Count;
			string strDownloadLocation = Settings.DownloadLocation;
			string strNewspaperLocation = GetDownloadLocation(Settings.DownloadLocation);

			// download the feeds
			foreach (Feed objFeed in Settings.Feeds) {
				objFeed.ProgressChanged += ReportFeedProgress;

				DateTime? dtLastDownloaded = objFeed.LastDownloaded;

				// download recent entries of the feed
				objFeed.DownloadRecentEntries(strNewspaperLocation, Settings.OnlyDownloadNewContent);

				_intCurrentFeed++;
			}

			ReportProgress(99, "Writing Files");

			// get feeds that need to be written
			var lstFeedsWithContent = from feed in Settings.Feeds
									  where feed.ContentSource != ContentSource.ContentSourcesEnum.None
									  orderby feed.DisplayOrder
									  select feed;

			IEnumerable<Entry> lstEntriesWithContent = lstFeedsWithContent.SelectMany(feed => feed.RecentlyDownloaded);

			IList<Entry> lstEntriesWithContentList = lstEntriesWithContent.ToList();

			// write feeds that need to be written
			for (int i = 0; i < lstEntriesWithContentList.Count; i++) {
				string strPreviousUrl = i == 0 ? "index.html" : lstEntriesWithContentList[i - 1].OsSafeTitle + ".html";
				string strNextUrl = i == lstEntriesWithContentList.Count - 1 ? null : lstEntriesWithContentList[i + 1].OsSafeTitle + ".html";
				lstEntriesWithContentList[i].WriteContentFile(strNewspaperLocation, strPreviousUrl, strNextUrl);
			}

			// write index.html
			string strSecondUrl = null;
			if (lstEntriesWithContentList.Count > 0)
				strSecondUrl = lstEntriesWithContentList[0].OsSafeTitle + ".html";
			WriteIndexXHtml(strNewspaperLocation, strSecondUrl);

			// write manifest.xml
			WriteManifestXml(strNewspaperLocation, lstEntriesWithContent);

			if (!Settings.OnlyDownloadNewContent) {
				WriteEmbeddedResource(strDownloadLocation, "DailyRssDownload.Lib.IndexStyle.css", "IndexStyle.css");
				WriteEmbeddedResource(strDownloadLocation, "DailyRssDownload.Lib.ContentStyle.css", "ContentStyle.css");
			}

			// always reset whether we are downloading all content to just new content
			Settings.OnlyDownloadNewContent = true;

			// save the settings
			Settings.Save();

			ReportProgress(100, "Done");
		}

		private void WriteManifestXml(string strDownloadLocation, IEnumerable<Entry> lstEntriesWithContent) {
			int i = 2;
			IEnumerable<XElement> lstPages = from page in lstEntriesWithContent
											 select new XElement("page",
												 new XAttribute("number", String.Format("{0:0000}", i++)),
												 new XAttribute("url", page.OsSafeTitle + ".html"));

			XDocument xdocManifest = new XDocument(
				new XDeclaration("1.0", "utf-8", "no"),
				new XElement("package",
					new XElement("metadata",
						new XElement("dc-metadata",
							new XElement("Title", this.NewsTitle),
							new XElement("Description", this.NewsDateDesc),
							new XElement("Date", this.NewsDate.ToString(DC_METADATA_DATE_FORMAT)),
							new XElement("Format", "text/html"),
							new XElement("Language", "en"),
							new XElement("Type", "newspaper")
							),
						new XElement("y-metadata",
							new XElement("FileSize", "000"),
							new XElement("ItemSize", "000"),
							new XElement("startpage", "index.html"),
							new XElement("version", "000")
							)
						),

					new XElement("index",
						new XElement("pagelist",
							new XAttribute("type", "overview"),
							new XAttribute("count", lstEntriesWithContent.Count() + 1),
							new XElement("page",
								new XAttribute("number", "0001"),
								new XAttribute("url", "index.html")
								),
							lstPages
							)
						)
				));

			xdocManifest.Save(Path.Combine(strDownloadLocation, "manifest.xml"));
		}

		private void WriteIndexXHtml(string strDownloadLocation, string strSecondUrl) {
			// get the items that were published since the last download (i.e. their published date was AFTER the
			//		LastLastDownload (since the LastDownload is Now()) and group them by feed
			IEnumerable<XElement> lstNewlyPublishedFeeds = from feed in Settings.Feeds
														   orderby feed.DisplayOrder
														   select feed.RenderAsXhtml();

			XNamespace ns = "http://www.w3.org/1999/xhtml";
			XDocumentType docType = new XDocumentType(
				"html",
				"-//W3C//DTD HTML 4.0 Transitional//EN",
				"http://www.w3.org/TR/html4/loose.dtd",
				null);

			XElement xelementHtml = new XElement(ns + "html");

			// head
			XElement head = new XElement("head");
			head.Add(new XElement("link",
					new XAttribute("type", "text/css"),
					new XAttribute("rel", "Stylesheet"),
					new XAttribute("href", "../IndexStyle.css")));
			if (!String.IsNullOrEmpty(strSecondUrl)) {
				head.Add(new XElement("link",
						new XAttribute("rel", "next"),
						new XAttribute("href", strSecondUrl)));
			}
			head.Add(new XElement("title", "My Daily News"));
			head.Add(new XElement("meta",
				new XAttribute("name", "keywords"),
				new XAttribute("type", "overview")));

			xelementHtml.Add(head);

			// body
			xelementHtml.Add(new XElement("body",
				new XElement("div", new XAttribute("class", "NewsHeader"),
					new XElement("h1", this.NewsTitle),
					new XElement("span", new XAttribute("class", "DailyRss_Date"), this.NewsDateDesc)),
				lstNewlyPublishedFeeds
			));

			XDocument xdocHtml = new XDocument(
				new XDeclaration("1.0", "utf-8", "no"),
				docType,
				xelementHtml);

			string strIndexAsHtml = xdocHtml.ToString(SaveOptions.DisableFormatting);

			// remove the commented out Summaries that may not have been correct XHTML
			strIndexAsHtml = strIndexAsHtml.Replace("<!--", "").Replace("-->", "");

			if (!Directory.Exists(strDownloadLocation))
				Directory.CreateDirectory(strDownloadLocation);
			
			File.WriteAllText(Path.Combine(strDownloadLocation, "index.html"), strIndexAsHtml);
		}

		private static void WriteEmbeddedResource(string strDownloadLocation, string strResourceName, string strOutputFileName) {
			// write out the IndexStyle.css if it doesn't exist
			if (!File.Exists(Path.Combine(strDownloadLocation, strOutputFileName))) {
				Assembly assembly = typeof(Newspaper).Assembly;
				Stream s = assembly.GetManifestResourceStream(strResourceName);
				StreamReader sr = new StreamReader(s);
				StreamWriter sw = new StreamWriter(Path.Combine(strDownloadLocation, strOutputFileName));
				sw.Write(sr.ReadToEnd());
				sw.Close();
				sr.Close();
			}
		}
	}
}
